import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Server implements Runnable {
    // default settings
    static boolean useCacheDefault = true;
    static int cacheLimitInDaysDefault = 2;
    static int threadPoolSizeDefault = 10;
    static String remoteDnsServerDefault = "223.5.5.5";

    private boolean useCache = true;
    private int cacheLimitInDays;
    private int threadPoolSize;
    private String remoteDnsServer;
    private boolean running = false;
    private ExecutorService pool;
    private ScheduledExecutorService executorService;
    private DatagramSocket socket;
    private Log log;

    public Server(int cacheLimitInDays, int threadPoolSize, String remoteDnsServer, Log log) {
        this.cacheLimitInDays = cacheLimitInDays;
        this.threadPoolSize = threadPoolSize;
        this.remoteDnsServer = remoteDnsServer;
        this.log = log;
    }

    @Override
    public void run() {
        running = true;
        // start logging
        log.addLog("dns-relay server starting...");

        byte[] buf = new byte[1024];
        DatagramPacket request = new DatagramPacket(buf, buf.length);
        log.addLog("\tuse-cache: " + useCache);
        log.addLog("\tcache-limit-in-days: " + cacheLimitInDays);
        log.addLog("\tthread-pool-size: " + threadPoolSize);
        log.addLog("\tremote-dns-server: " + remoteDnsServer);
        log.writeLog();

        Cache cache = new Cache();

        try {
            socket = new DatagramSocket(530);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        log.addLog("socket connected");

        //dns threads
        pool = Executors.newFixedThreadPool(threadPoolSize);
        //flush cache thread
        executorService = Executors.newScheduledThreadPool(1);
        executorService.scheduleAtFixedRate(() -> {
            cache.flushCacheFile(cacheLimitInDays);
            log.addLog("dns cache flushed and loaded");
            log.writeLog();
        }, 0, 1, TimeUnit.DAYS);

        // wait for cache loaded
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.addLog("server started");
        while (running) {
            try {
                socket.receive(request);
            } catch (IOException e) {
                if (running) {
                    e.printStackTrace();
                }
            }
            if (running) {
                pool.execute(new Handler(request, socket, cache, new Log(), remoteDnsServer, useCache));
            }
        }
    }

    public void stop() {
        running = false;
        if (socket != null) {
            socket.close();
        }
        log.addLog("socket closed");

        if (pool != null) {
            pool.shutdown();
        }
        log.addLog("pool shutdown");

        if (executorService != null) {
            executorService.shutdown();
        }
        log.addLog("executor service shutdown");

        log.addLog("server stopped");
    }
}